package peerToPeerFacebook.proj;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/** Class that can invoke dynamic functions on given object. The main value of this function is figuring out
 * which method to call and providing some debug information. */
public class DynamicInvoker
{
    /** Target object to make calls on */ 
    protected Object dispatchObject = null;
    
    /** Constructs the class for the given target object,
     * 
     * @param dispatchObject Target object to use in invoking calls.
     */
    public DynamicInvoker(Object dispatchObject)
    {
        this.dispatchObject = dispatchObject;
    }
    
    /** Invoke dispatcher function.
     * 
     * @param methodName Name of the method to call
     * @param params Params to send to the method call
     * @return
     */
    public Object invoke(String methodName, Serializable[] params) 
            throws IllegalAccessException, InvocationTargetException, RPCException
    {
        Class<?> dispatchClass = this.dispatchObject.getClass();    
        
        Method[] declaredMethods = dispatchClass.getDeclaredMethods();
        Method method = null;
        
        // Find the method to invoke
        for (int i = 0; i < declaredMethods.length; i++)
        {
            if (verifyMethodName(declaredMethods[i], methodName, params))
            {
                method = declaredMethods[i];
            }
        }
        
        if (method == null)
        {
            assert logMethods(methodName, params, null): "Failed to find required method";
            throw new RPCError("RPCServer Invoke: No method found with the name '" + methodName + "'");
        }
        
        
        try {
            return method.invoke(dispatchObject, (Object[])params);
        } catch (IllegalArgumentException ex)
        {
            // Purely for debugging purpose intercept this exception and print info about it
            assert logMethods(methodName, params, ex): "Failed to invoke method";
            throw ex;
        }
    }
    
    /** Verifies if the given method matches the expected method name and number of arguments
     * @param actualMethod
     * @param expectedMethodName
     * @param expectedParams
     * @return true if there is a match
     */
    private boolean verifyMethodName(Method actualMethod, String expectedMethodName, Object[] expectedParams)
    {   
        
        if (!actualMethod.getName().equals(expectedMethodName))
            return false;
        
        return actualMethod.getParameterTypes().length == expectedParams.length;
    }
    
    /** Prints debug information about what method was tried to be invoked, and what where available. This method
     * always retuns false so it could be used in assert statement. This allows to print this information iff asserts
     * are enabled.
     * 
     * @param method Name of the method that was about to be executed
     * @param params Params to that method call
     * @param ex Exception (if any) that should be also printed.
     * 
     * @return false
     */
    protected boolean logMethods(String method, Object[] params, Exception ex)
    {
        // create nice method definition
        String methodDefinition = method + "(";
        String separator = "";
        for(Object param: params)
        {
            methodDefinition += separator + param.getClass().getName();
            separator = ", ";
        }
        
        methodDefinition += ")";
                
        System.err.println("RPC: Server failed to find object to invoke.");
        System.err.println("\tLooking for method: " + methodDefinition);
        System.err.println("\tFound methods:");
        
        for (Method m: dispatchObject.getClass().getDeclaredMethods())
        {
            methodDefinition = m.getName() + "(";
            separator = "";
            for (Class<?> param: m.getParameterTypes())
            {
                methodDefinition += separator + param.getName();
                separator = ", ";
            }
            
            methodDefinition += ")";
            System.err.println(methodDefinition);
        }
        
        if (ex != null)
            ex.printStackTrace();
        
        return false;
    }

}
